//===-- AppleObjCRuntimeV2.h ------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef liblldb_AppleObjCRuntimeV2_h_
#define liblldb_AppleObjCRuntimeV2_h_

// C Includes
// C++ Includes
#include <map>
#include <memory>

// Other libraries and framework includes
// Project includes
#include "lldb/lldb-private.h"
#include "lldb/Target/ObjCLanguageRuntime.h"
#include "AppleObjCRuntime.h"

class RemoteNXMapTable;

namespace lldb_private {

class AppleObjCRuntimeV2 :
        public AppleObjCRuntime
{
public:
    ~AppleObjCRuntimeV2() override = default;

    //------------------------------------------------------------------
    // Static Functions
    //------------------------------------------------------------------
    static void
    Initialize();
    
    static void
    Terminate();
    
    static lldb_private::LanguageRuntime *
    CreateInstance (Process *process, lldb::LanguageType language);
    
    static lldb_private::ConstString
    GetPluginNameStatic();

    static bool classof(const ObjCLanguageRuntime* runtime)
    {
        switch (runtime->GetRuntimeVersion())
        {
            case ObjCRuntimeVersions::eAppleObjC_V2:
                return true;
            default:
                return false;
        }
    }

    // These are generic runtime functions:
    bool
    GetDynamicTypeAndAddress(ValueObject &in_value,
                             lldb::DynamicValueType use_dynamic,
                             TypeAndOrName &class_type_or_name,
                             Address &address,
                             Value::ValueType &value_type) override;

    bool
    GetDynamicTypeAndAddress(ValueObject &in_value,
                             lldb::DynamicValueType use_dynamic,
                             TypeAndOrName &class_type_or_name,
                             Address &address,
                             Value::ValueType &value_type,
                             bool allow_swift) override;

    UtilityFunction *
    CreateObjectChecker(const char *) override;

    //------------------------------------------------------------------
    // PluginInterface protocol
    //------------------------------------------------------------------
    ConstString
    GetPluginName() override;
    
    uint32_t
    GetPluginVersion() override;
    
    ObjCRuntimeVersions
    GetRuntimeVersion() const override
    {
        return ObjCRuntimeVersions::eAppleObjC_V2;
    }

    size_t
    GetByteOffsetForIvar(CompilerType &parent_qual_type, const char *ivar_name) override;

    void
    UpdateISAToDescriptorMapIfNeeded() override;
    
    ConstString
    GetActualTypeName(ObjCLanguageRuntime::ObjCISA isa) override;
    
    ClassDescriptorSP
    GetClassDescriptor(ValueObject& in_value) override;
    
    ClassDescriptorSP
    GetClassDescriptorFromISA(ObjCISA isa) override;
    
    DeclVendor *
    GetDeclVendor() override;
    
    lldb::addr_t
    LookupRuntimeSymbol(const ConstString &name) override;
    
    EncodingToTypeSP
    GetEncodingToType() override;

    bool
    IsTaggedPointer(lldb::addr_t ptr);

    TaggedPointerVendor*
    GetTaggedPointerVendor() override
    {
        return m_tagged_pointer_vendor_ap.get();
    }
    
    // none of these are valid ISAs - we use them to infer the type
    // of tagged pointers - if we have something meaningful to say
    // we report an actual type - otherwise, we just say tagged
    // there is no connection between the values here and the tagged pointers map
    static const ObjCLanguageRuntime::ObjCISA g_objc_Tagged_ISA = 1;
    static const ObjCLanguageRuntime::ObjCISA g_objc_Tagged_ISA_NSAtom = 2;
    static const ObjCLanguageRuntime::ObjCISA g_objc_Tagged_ISA_NSNumber = 3;
    static const ObjCLanguageRuntime::ObjCISA g_objc_Tagged_ISA_NSDateTS = 4;
    static const ObjCLanguageRuntime::ObjCISA g_objc_Tagged_ISA_NSManagedObject = 5;
    static const ObjCLanguageRuntime::ObjCISA g_objc_Tagged_ISA_NSDate = 6;

protected:
    lldb::BreakpointResolverSP
    CreateExceptionResolver(Breakpoint *bkpt, bool catch_bp, bool throw_bp) override;

private:
    class HashTableSignature
    {
    public:
        HashTableSignature ();

        bool
        NeedsUpdate (Process *process,
                     AppleObjCRuntimeV2 *runtime,
                     RemoteNXMapTable &hash_table);
        
        void
        UpdateSignature (const RemoteNXMapTable &hash_table);

    protected:
        uint32_t m_count;
        uint32_t m_num_buckets;
        lldb::addr_t m_buckets_ptr;
    };

    class NonPointerISACache
    {
    public:
        static NonPointerISACache*
        CreateInstance (AppleObjCRuntimeV2& runtime,
                        const lldb::ModuleSP& objc_module_sp);
        

        ObjCLanguageRuntime::ClassDescriptorSP
        GetClassDescriptor (ObjCISA isa);

    private:
        NonPointerISACache (AppleObjCRuntimeV2& runtime,
                            uint64_t objc_debug_isa_class_mask,
                            uint64_t objc_debug_isa_magic_mask,
                            uint64_t objc_debug_isa_magic_value);
        
        bool
        EvaluateNonPointerISA (ObjCISA isa, ObjCISA& ret_isa);
        
        AppleObjCRuntimeV2&                                         m_runtime;
        std::map<ObjCISA,ObjCLanguageRuntime::ClassDescriptorSP>    m_cache;
        uint64_t                                                    m_objc_debug_isa_class_mask;
        uint64_t                                                    m_objc_debug_isa_magic_mask;
        uint64_t                                                    m_objc_debug_isa_magic_value;

        friend class AppleObjCRuntimeV2;
        
        DISALLOW_COPY_AND_ASSIGN(NonPointerISACache);
    };
    
    class TaggedPointerVendorV2 : public ObjCLanguageRuntime::TaggedPointerVendor
    {
    public:
        ~TaggedPointerVendorV2() override = default;

        static TaggedPointerVendorV2*
        CreateInstance (AppleObjCRuntimeV2& runtime,
                        const lldb::ModuleSP& objc_module_sp);

    protected:
        AppleObjCRuntimeV2&                                         m_runtime;
        
        TaggedPointerVendorV2 (AppleObjCRuntimeV2& runtime) :
        TaggedPointerVendor(),
        m_runtime(runtime)
        {
        }

    private:
        DISALLOW_COPY_AND_ASSIGN(TaggedPointerVendorV2);
    };
    
    class TaggedPointerVendorRuntimeAssisted : public TaggedPointerVendorV2
    {
    public:
        bool
        IsPossibleTaggedPointer(lldb::addr_t ptr) override;
        
        ObjCLanguageRuntime::ClassDescriptorSP
        GetClassDescriptor(lldb::addr_t ptr) override;

    protected:
        TaggedPointerVendorRuntimeAssisted (AppleObjCRuntimeV2& runtime,
                                             uint64_t objc_debug_taggedpointer_mask,
                                             uint32_t objc_debug_taggedpointer_slot_shift,
                                             uint32_t objc_debug_taggedpointer_slot_mask,
                                             uint32_t objc_debug_taggedpointer_payload_lshift,
                                             uint32_t objc_debug_taggedpointer_payload_rshift,
                                             lldb::addr_t objc_debug_taggedpointer_classes);
        
        typedef std::map<uint8_t,ObjCLanguageRuntime::ClassDescriptorSP> Cache;
        typedef Cache::iterator CacheIterator;
        Cache                                                       m_cache;
        uint64_t                                                    m_objc_debug_taggedpointer_mask;
        uint32_t                                                    m_objc_debug_taggedpointer_slot_shift;
        uint32_t                                                    m_objc_debug_taggedpointer_slot_mask;
        uint32_t                                                    m_objc_debug_taggedpointer_payload_lshift;
        uint32_t                                                    m_objc_debug_taggedpointer_payload_rshift;
        lldb::addr_t                                                m_objc_debug_taggedpointer_classes;
        
        friend class AppleObjCRuntimeV2::TaggedPointerVendorV2;
        
        DISALLOW_COPY_AND_ASSIGN(TaggedPointerVendorRuntimeAssisted);
    };
    
    class TaggedPointerVendorExtended : public TaggedPointerVendorRuntimeAssisted
    {
        public:
        ObjCLanguageRuntime::ClassDescriptorSP
        GetClassDescriptor(lldb::addr_t ptr) override;

        protected:
        TaggedPointerVendorExtended (AppleObjCRuntimeV2& runtime,
                                     uint64_t objc_debug_taggedpointer_mask,
                                     uint64_t objc_debug_taggedpointer_ext_mask,
                                     uint32_t objc_debug_taggedpointer_slot_shift,
                                     uint32_t objc_debug_taggedpointer_ext_slot_shift,
                                     uint32_t objc_debug_taggedpointer_slot_mask,
                                     uint32_t objc_debug_taggedpointer_ext_slot_mask,
                                     uint32_t objc_debug_taggedpointer_payload_lshift,
                                     uint32_t objc_debug_taggedpointer_payload_rshift,
                                     uint32_t objc_debug_taggedpointer_ext_payload_lshift,
                                     uint32_t objc_debug_taggedpointer_ext_payload_rshift,
                                     lldb::addr_t objc_debug_taggedpointer_classes,
                                     lldb::addr_t objc_debug_taggedpointer_ext_classes);

        bool
        IsPossibleExtendedTaggedPointer (lldb::addr_t ptr);

        typedef std::map<uint8_t,ObjCLanguageRuntime::ClassDescriptorSP> Cache;
        typedef Cache::iterator CacheIterator;
        Cache                                                       m_ext_cache;
        uint64_t                                                    m_objc_debug_taggedpointer_ext_mask;
        uint32_t                                                    m_objc_debug_taggedpointer_ext_slot_shift;
        uint32_t                                                    m_objc_debug_taggedpointer_ext_slot_mask;
        uint32_t                                                    m_objc_debug_taggedpointer_ext_payload_lshift;
        uint32_t                                                    m_objc_debug_taggedpointer_ext_payload_rshift;
        lldb::addr_t                                                m_objc_debug_taggedpointer_ext_classes;

        friend class AppleObjCRuntimeV2::TaggedPointerVendorV2;

        DISALLOW_COPY_AND_ASSIGN(TaggedPointerVendorExtended);
    };
    
    class TaggedPointerVendorLegacy : public TaggedPointerVendorV2
    {
    public:
        bool
        IsPossibleTaggedPointer(lldb::addr_t ptr) override;
        
        ObjCLanguageRuntime::ClassDescriptorSP
        GetClassDescriptor (lldb::addr_t ptr) override;

    protected:
        TaggedPointerVendorLegacy (AppleObjCRuntimeV2& runtime) :
        TaggedPointerVendorV2 (runtime)
        {
        }
        
        friend class AppleObjCRuntimeV2::TaggedPointerVendorV2;
        
        DISALLOW_COPY_AND_ASSIGN(TaggedPointerVendorLegacy);
    };
    
    struct DescriptorMapUpdateResult
    {
        bool update_ran;
        bool any_found;
        
        DescriptorMapUpdateResult (bool ran,
                                   bool found)
        {
            update_ran = ran;
            any_found = found;
        }
        
        static DescriptorMapUpdateResult
        Fail ()
        {
            return {false, false};
        }
        
        static DescriptorMapUpdateResult
        Success ()
        {
            return {true, true};
        }
    };

    AppleObjCRuntimeV2 (Process *process,
                        const lldb::ModuleSP &objc_module_sp);

    ObjCISA
    GetPointerISA (ObjCISA isa);

    lldb::addr_t
    GetISAHashTablePointer ();

    bool
    UpdateISAToDescriptorMapFromMemory (RemoteNXMapTable &hash_table, uint32_t &discovered_classes_count);
    
    bool
    UpdateISAToDescriptorMapDynamic(RemoteNXMapTable &hash_table, uint32_t &discovered_classes_count);
    
    uint32_t
    ParseClassInfoArray (const lldb_private::DataExtractor &data,
                         uint32_t num_class_infos);
    
    DescriptorMapUpdateResult
    UpdateISAToDescriptorMapSharedCache ();
    
    void
    WarnIfNoClassesFound (bool globally);

    lldb::addr_t
    GetSharedCacheReadOnlyAddress();
    
    friend class ClassDescriptorV2;
    friend class SwiftLanguageRuntime;

    std::unique_ptr<UtilityFunction>        m_get_class_info_code;
    lldb::addr_t                            m_get_class_info_args;
    Mutex                                   m_get_class_info_args_mutex;

    std::unique_ptr<UtilityFunction>        m_get_shared_cache_class_info_code;
    lldb::addr_t                            m_get_shared_cache_class_info_args;
    Mutex                                   m_get_shared_cache_class_info_args_mutex;

    std::unique_ptr<DeclVendor>             m_decl_vendor_ap;
    lldb::addr_t                            m_isa_hash_table_ptr;
    HashTableSignature                      m_hash_signature;
    bool                                    m_has_object_getClass;
    bool                                    m_loaded_objc_opt;
    std::unique_ptr<NonPointerISACache>     m_non_pointer_isa_cache_ap;
    std::unique_ptr<TaggedPointerVendor>    m_tagged_pointer_vendor_ap;
    EncodingToTypeSP                        m_encoding_to_type_sp;
    struct {
        bool in_shared_cache : 1;
        bool globally : 1;
    }                                       m_warn_if_no_classes_cached;
    bool                                    m_noclasses_warning_emitted;
};
    
} // namespace lldb_private

#endif // liblldb_AppleObjCRuntimeV2_h_
